<?php


namespace coco\queue;

use coco\App;
use coco\Log;
use SplFileInfo;

final class Monitor
{

    private $terminate = false; //是否中断
    private $queue;
    private $last_check_time;
    private $cd;
    private $last_mtime;
    private $too_many_files_check;

    public function __construct($queue, array $option = [])
    {
        $this->queue = $queue;
        cli_set_process_title('coco queue ' . $this->queue . ' monitor start_file=' . dirname(App::getInstance()->app_path));
        $this->initSignal();
        $this->log('start:' . posix_getpid() . ' ' . posix_getppid());
        $this->last_check_time = time();
        $this->cd = $option['cd'] ?? 60;
    }

    public function __destruct()
    {
        $this->log('stop:' . posix_getpid() . ' ' . posix_getppid());
    }

    public function run()
    {
        while (1) {
            pcntl_signal_dispatch();
            coco_sleep(0.01); //防止cpu长红
            if ($this->terminate) {
                break;
            }
            $this->checkFilesChange(App::getInstance()->app_path);
        }
    }

    private function log($msg)
    {
        Log::getInstanceByKey('queue')->write('queue ' . $this->queue . ' monitor :' . $msg);
    }


    private function initSignal()
    {
        pcntl_signal(SIGUSR1, SIG_DFL);
        pcntl_signal(SIGCHLD, SIG_DFL);
        pcntl_signal(SIGTERM, [$this, 'signalHandler']);
        pcntl_signal(SIGHUP, [$this, 'signalHandler']);
        pcntl_signal(SIGQUIT, [$this, 'signalHandler']);
    }

    //信号处理函数
    private function signalHandler($signo)
    {
        switch ($signo) {
                //中断进程
            case SIGTERM:
            case SIGHUP:
            case SIGQUIT:
                $this->terminate = true;
                break;
            default:
        }
    }

    private function checkFilesChange($monitor_dir)
    {
        if ($this->last_check_time + $this->cd > time()) {
            return;
        }
        $this->last_check_time = time();
        if (!$this->last_mtime) {
            $this->last_mtime = time();
        }
        clearstatcache();
        if (!is_dir($monitor_dir)) {
            if (!is_file($monitor_dir)) {
                return;
            }
            $iterator = [new SplFileInfo($monitor_dir)];
        } else {
            // recursive traversal directory
            $dir_iterator = new \RecursiveDirectoryIterator($monitor_dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
            $iterator = new \RecursiveIteratorIterator($dir_iterator);
        }
        $count = 0;
        foreach ($iterator as $file) {
            $count++;
            /**@var SplFileInfo $file */
            if (is_dir($file)) {
                continue;
            }
            // check mtime
            if ($this->last_mtime < $file->getMTime() && in_array($file->getExtension(), ['php'])) {
                $var = 0;
                exec('"' . PHP_BINARY . '" -l ' . $file, $out, $var);
                if ($var) {
                    $this->last_mtime = $file->getMTime();
                    continue;
                }
                $this->last_mtime = $file->getMTime();
                $this->log("$file 更新 即将重启worker");
                posix_kill(posix_getppid(), SIGUSR1);
                break;
            }
        }
        if (!$this->too_many_files_check && $count > 1000) {
            $this->log("$monitor_dir 检测太多文件($count files) 将放慢检测速度检测) ");
            $this->too_many_files_check = 1;
            $this->cd = $this->cd * ceil($count / 1000);
        }
    }
}
